<?php

class QPlex
{
    private $tplPath;
    private $varsArray = array();
    private $cacheDir;
    private $extendView;
    private $extendBlock;

    function __construct($path, $vars = array())
    {
        $this->tplPath = $path;
        $this->cacheDir = QXC::pathExist(QXC()->Config->cache);

        if ($vars && is_array($vars))
        {
            $this->varsArray = $vars;
        }
    }

    public function SetVars($array)
    {
        $this->varsArray = $array;
    }

    public function SetExtend($view, $block)
    {
        $this->extendView = $view;
        $this->extendBlock = $block;
    }

    public function Prepare()
    {
        $cachedName = $this->cacheDir . "/views/" . filemtime($this->tplPath) . "-" . basename($this->tplPath);

        if(!DEBUG && file_exists($cachedName))
        {
            $content = file_get_contents($cachedName);
        }
        else
        {
            $content = file_get_contents($this->tplPath);

            $content = str_replace(array("\{", "\}"), array(chr(0), chr(1)), $content);

            $content = $this->parseBlocks($content);

            $content = $this->parseTags($content);

            $content = str_replace(array(chr(0), chr(1)), array("{", "}"), $content);

            if(DEBUG) // Put markers into output
            {
                $cn = basename($cachedName);

            //                $content = join(array(PHP_EOL,
            //                    "<!-- QXC[{$cn}] -->",
            //                    PHP_EOL,
            //                    $content,
            //                    PHP_EOL,
            //                    "<!-- /QXC[{$cn}] -->"));
            }
            else
            {
                $content = str_replace("\n", "", $content);
            }

            //            if($extendView = $this->extendView($content))
            //            {
            //                View::Load($temp, $this->varsArray, dirname($this->tplPath));
            //            }

            file_put_contents($cachedName, $content);
        }

        $content = $this->exec($cachedName);

        if($this->extendView)
        {
            $this->SetBlock($this->extendBlock, $content);

            return View::Load($this->extendView, $this->varsArray);
        }

        return $content;
    }

    public function Render()
    {
        $content = $this->Prepare();

        echo $content;
    }

    private function parseTags($str, $nested = false)
    {
        $rx = $nested ? "/{([^\n]+)}/U" : "/{([^\n]+)}/";

        if(preg_match_all($rx, $str, $matches, PREG_SET_ORDER))
        {
            foreach($matches as $tag)
            {
                $var = $this->parseTags($tag[1], true);

                $arr = preg_split("/[\(]+|[\s]+|[\:]+/", $var, 2);

                switch ($key = array_shift($arr))
                {
                    case 'if':
                    case 'for':
                    case 'foreach':
                    case 'while':
                        $var = "{$key}({$arr[0]}):";
                        break;
                    case '/if':
                    case '/for':
                    case '/foreach':
                    case '/while':
                        $var = "end" . trim($key, "/") . ";";
                        break;
                    case 'else':
                        $var = "else:";
                        break;
                    case 'true':
                    case 'false':
                        $var = "{$key}";
                        break;
                    case 'php':
                        $var = "<?php ";
                        $nested = true;
                        break;
                    case '/php':
                        $var = " ?>";
                        $nested = true;
                        break;
                    case 'module':
                        $arr = explode(".", $arr[0]);

                        $var = "Module::Load(\"" . array_shift($arr) . "\")->" . join("->", $arr);

                        if(!$nested)
                        {
                            $var = "echo {$var};";
                        }
                        break;
                    case 'view':
                        $var = "View::Load(\"{$arr[0]}\", \$this->view->VarsArray, \$this->view->ViewPath)->Render()";
                        break;
                    case 'block':
                        $var = "if(!getBlock(\"{$arr[0]}\", \$this->view->VarsArray)):";
                        break;
                    case 'block_set':
                        $var = "if(false):";
                        break;
                    case '/block':
                    case '/block_set':
                        $var = "endif;";
                        break;
                    case 'extend':
                        $var = "View::Load(\"{$arr[0]}\", \$this->view->VarsArray, \$this->view->ViewPath)->Render()";
                        break;
                    case 'script':
                        QXC()->setGlobal(null, $arr[0], "SCRIPTS");
                        $var = "";
                        break;
                    case 'style':
                        QXC()->setGlobal(null, $arr[0], "STYLES");
                        $var = "";
                        break;
                    case 'lang':
                        $parts = explode("|", $arr[0]);

                        $var = "\"" . __(array_shift($parts)) . "\"";

                        foreach ($parts as $part)
                        {
                            $var = "{$part}($var)";
                        }
                        
                        $var = "echo {$var}";
                        break;
                    case 'qxc':
                        $var = str_replace(".", "->", $arr[0]);
                        $var = "QXC()->{$var}";

                        if(!$nested)
                        {
                            $var = "echo {$var};";
                        }
                        break;
                    default:
                        $var = "{$key}";

                        if(!preg_match("/\(|\)/", $key))
                        {
                            $var = "\${$var}";
                        }

                        // Handle object subsets
                        $var = str_replace(".", "->", $var);

                        if($arr)
                        {
                            $parts = explode("|", $arr[0]);

                            foreach ($parts as $part)
                            {
                                $var = "{$part}($var)";
                            }
                        }

                        if(!$nested)
                        {
                            $var = "echo {$var};";
                        }
                        break;
                }

                if(!$nested)
                {
                    $var = '<?php ' . $var . ' ?>';
                }

                $str = preg_replace("/" . preg_quote($tag[0], "/") . "/", $var, $str, 1); // One replace each time
            }
        }

        return $str;
    }

    private function parseBlocks($content)
    {
        $patterns = array(
            "/{block_set\:([\w\-]+)}(.*){\/block_set}/s"
        );
        $replacements = array(
            "<?php \$html =<<<HTML r \nHTML;\n ?>"
        );

        if(preg_match_all("/{block_set\:([\w\-]+)}(.*){\/block_set}/Us", $content, $matches, PREG_SET_ORDER))
        {
            foreach ($matches as $val)
            {
                $this->SetBlock($val[1], $val[2]);
            }
        }

        //        $content = preg_replace($patterns, $replacements, $content);

        return $content;
    }

    // Magic...
    function __toString()
    {
        return $this->Prepare();
    }

    private function exec($path)
    {
    // Begin output buffering
        ob_start();

        extract($this->varsArray, EXTR_SKIP);

        require_once ($path);

        return ob_get_clean();
    }

    public function SetBlock($name, $data)
    {
        if(is_array($name))
        {
            $data = View::Load(current($name), $this->varsArray, $data)->Content();
            $name = key($name);
        }

        $data = $this->parseTags($data);
        QXC()->setGlobal($name, $data, "BLOCKS");
    }
}

// Helper functions
function escape($str)
{
    return htmlentities($str);
}

// Helper functions
function format($str)
{
    return date("d M Y h:i A", (int)$str);
}

function format_short($str)
{
    return date("d M Y", (int)$str);
}

function getBlock($name, $vars = array())
{
// TODO: review
    extract($vars, EXTR_SKIP);

    if($modulePath = QXC::getGlobal("ModuleName", "QXC"))
    {
        $modulePrefix = "{$modulePath}:";
    }

    switch ($name)
    {
        case 'scripts':
            $scriptsArray = QXC::getGlobal(null, "SCRIPTS");

            if ((bool)$scriptsArray)
            {
                $scripts = join(',', array_unique($scriptsArray));

                $out .= "<script type=\"text/javascript\" src=\"" . WEB_URL . "/cache/js?q={$modulePrefix}{$scripts}\"></script>\n";
            }
            break;
        case 'styles':
        // Generate links to external scripts/styles
            $stylesArray = QXC::getGlobal(null, "STYLES");

            if ((bool)$stylesArray)
            {
                $styles = join(',', array_unique($stylesArray));

                $out .= "<link rel=\"stylesheet\" href=\"" . WEB_URL . "/cache/css?q={$modulePrefix}{$styles}\" type=\"text/css\" />\n";
            }
            break;
        case 'footer':
            $validationArray = QXC::getGlobal(null, 'VALIDATION');

            if((bool)$validationArray)
            {
                $out .= "<script type=\"text/javascript\">";
                $out .= "$(document).ready(function() {";

                foreach ($validationArray as $key=>$val)
                {
                    $out .= "$('input[name=\"{$key}\"]').validate({
                    regex: {$val[0]},
                    message: '{$val[1]}'});\n";
                }

                $out .= "});";
                $out .= "</script>";
            }

            // Display erros, validation etc.
            $errorsArray = QXC::getGlobal(null, 'ERRORS', true);
            $messagesArray = QXC::getGlobal(null, 'MESSAGES', true);

            if ((bool)$errorsArray || (bool)$messagesArray)
            {
                QXC()->setGlobal(null, "jquery.gritter.css", "STYLES");
                QXC()->setGlobal(null, "jquery.gritter.js", "SCRIPTS");

                if ((bool)count($errorsArray))
                {
                    $q = new QView('gitter.block', array('dataArray' => $errorsArray));
                    $out .= (string)$q->Content();

                    // Clear notifications cache
                    QXC()->delGlobal("ERRORS", true);
                }

                if ((bool)count($messagesArray))
                {
                    $q = new QView('gitter.block', array('dataArray' => $messagesArray));
                    $out .= (string)$q->Content();

                    // Clear notifications cache
                    QXC()->delGlobal("MESSAGES", true);
                }
            }
            break;
    }

    // Add template parts
    $out .= $block = QXC::getGlobal($name, 'BLOCKS');

    if(!empty($out))
    {
        echo eval('?>' . $out . '<?');
    }

    if($block)
    {
        return true;
    }

    return false;
}